﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Activation;
using System.Web;
using System.Text;
using System.Security.Cryptography;

namespace BigMailer.Services
{
	[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
	public class MailerService : IMailerService
	{
		private const string AuthSalt =
			"There is big news on the horizon.............................................................................................";

		private const string UploadFolder = "Files";
		private const string DiscoverableExtension = ".discoverable";

		#region IMailerService Members

		public void BeginUpload(string file, string authToken)
		{
			ValidateUser( authToken );

			string destFile = GetDestinationFile( file );
			File.Create( destFile ).Dispose();
		}

		public void AddFilePart(string file, byte[] part, string authToken)
		{
			ValidateUser( authToken );

			string destFile = GetDestinationFile( file );
			if ( !File.Exists( destFile ) )
			{
				throw new FileNotFoundException( destFile );
			}

			using ( FileStream stream = new FileStream( destFile, FileMode.Append, FileAccess.Write, FileShare.None ) )
			{
				stream.Write( part, 0, part.Length );
				stream.Flush();
			}
		}

		public string CloseFileAndGetUrl(string file, string authToken)
		{
			ValidateUser( authToken );

			string destFile = GetDestinationFile( file );
			if ( !File.Exists( destFile ) )
			{
				throw new FileNotFoundException( destFile );
			}

			return GetDestinationUrl( destFile );
		}

		public void Ping()
		{
			// Something to call without side-effects to test client config.
		}

		public void AbandonFile(string file, string authToken)
		{
			ValidateUser( authToken );

			string destFile = GetDestinationFile( file );
			if ( File.Exists( destFile ) )
			{
				File.Delete( destFile );
			}

			string flag = GenerateDiscoverabilityFlag(file);
			if (File.Exists(flag))
			{
				File.Delete( flag );
			}
		}

		public ExistingFile[] GetPreexistingFiles(string authToken)
		{
			ValidateUser( authToken );

			string folder = Path.GetDirectoryName( GetDestinationFile( "temp.txt" ) );

			return
				(from file in Directory.GetFiles( folder )
				 where !file.EndsWith( DiscoverableExtension )
				 let info = new FileInfo( file )
				 orderby info.CreationTime descending
				 select
					 new ExistingFile
					 {
						 Name = info.Name,
						 Size = info.Length,
						 Created = info.CreationTime,
						 Url = GetDestinationUrl( file ),
						 IsDiscoverable = CheckForDiscoverabilityFlag( info.FullName )
					 }
				).ToArray();
		}

		public ExistingFile[] GetDiscoverableFiles(string authToken)
		{
			ValidateUser( authToken );

			string folder = Path.GetDirectoryName( GetDestinationFile( "temp.txt" ) );

			return
				(from file in Directory.GetFiles( folder )
				 let info = new FileInfo( file )
				 let discoverable = CheckForDiscoverabilityFlag( info.FullName )
				 where !file.EndsWith( DiscoverableExtension ) && discoverable
				 orderby info.CreationTime descending
				 select
					 new ExistingFile
					 {
						 Name = info.Name,
						 Size = info.Length,
						 Created = info.CreationTime,
						 Url = GetDestinationUrl( file ),
						 IsDiscoverable = discoverable
					 }
				).ToArray();
		}

		public string Authenticate(string user, string password)
		{
			return GenerateToken( user, password );
		}

		public void MakeFileDiscoverable(string file, string authToken)
		{
			ValidateUser( authToken );

			string flag = GenerateDiscoverabilityFlag( file );
			if ( File.Exists( flag ) )
			{
				return;
			}

			using ( StreamWriter sw = new StreamWriter( flag ) )
            {
				sw.WriteLine( "This file is a place holder." );
            }
		}

		public void MakeFileUndiscoverable(string file, string authToken)
		{
			ValidateUser( authToken );

			string flag = GenerateDiscoverabilityFlag( file );
			if (File.Exists(flag))
			{
				File.Delete( flag );
			}
		}

		#endregion

		private bool CheckForDiscoverabilityFlag(string file)
		{
			if (!Path.IsPathRooted(file))
			{
				file = GetDestinationFile( file );
			}
			return File.Exists( GenerateDiscoverabilityFlag( file ) );
		}

		private string GenerateDiscoverabilityFlag(string file)
		{
			if ( !Path.IsPathRooted( file ) )
			{
				file = GetDestinationFile( file );
			}

			return file + DiscoverableExtension;
		}

		private string GenerateToken(string user, string password)
		{
			string textInput = AuthSalt + user + AuthSalt + password + AuthSalt;
			byte[] bytes = Encoding.UTF8.GetBytes( textInput );
			SHA1 sha1 = SHA1.Create();
			byte[] output = sha1.ComputeHash( bytes );

			string token = Convert.ToBase64String( output );
			return token;
		}

		private string GetDestinationFile(string file)
		{
			string relFile = Path.GetFileName( file );
			string folder = HttpContext.Current.Server.MapPath( UploadFolder );
			return Path.Combine( folder, relFile );
		}

		private string GetDestinationUrl(string file)
		{
			string inboundUrl = HttpContext.Current.Request.Url.AbsoluteUri;
			int index = inboundUrl.LastIndexOf( '/' );
			string baseUrl = inboundUrl.Substring( 0, index + 1 );
			return baseUrl + UploadFolder + "/" + Path.GetFileName( file );
		}

		private void ValidateUser(string authToken)
		{
			if (!UserSettings.HasCredentials)
            {
				return;
            }

			string token = GenerateToken( UserSettings.UserName, UserSettings.Password );
			if (token != authToken)
            {
				AuthenticationException.ThrowFault( "Invalid authentication token" );
            }
		}
	}
}